﻿using GreedySnake.UI.View.ViewEx;
using System.Diagnostics;
using Timer = System.Windows.Forms.Timer;

namespace GreedySnake.UI.View
{
    public class SnakeMap : Control
    {
        private List<Cell> _snake;
        private Timer _timer;
        public SnakeMap()
        {
            SetStyle(
              ControlStyles.ResizeRedraw
              | ControlStyles.DoubleBuffer
              | ControlStyles.UserPaint
              | ControlStyles.AllPaintingInWmPaint
              | ControlStyles.SupportsTransparentBackColor,
              true
              );

            Reset();
            _timer = new Timer();
            _timer.Tick += _timer_Tick;
            _timer.Interval = 200;
        }
        public int Interval
        {
            get { return _timer.Interval; }
            set { _timer.Interval = value; }
        }
        private int _snakeCount;
        public event Action GameOverCallback;
        public event Action<int> SuccessCallback;
        Random _ran = new Random();
        private void _timer_Tick(object? sender, EventArgs e)
        {
            if (IsAI)
            {
                Play2();
                Play1();
            }
            else
                Play1();
        }
        public bool IsAI = false;
        private void Play2()
        {
            var firstPoint = _snake.First();
            int dx = TargetPoint.X - firstPoint.Point.X;
            int dy = 0;
            if (dx == 0)
            {
                dy = TargetPoint.Y - firstPoint.Point.Y;
                if (dy < 0)
                    SnakeDirection = Direction.Top;
                else if (dy > 0)
                    SnakeDirection = Direction.Bottom;
            }
            else
            {
                if (dx < 0)
                    SnakeDirection = Direction.Left;
                else
                    SnakeDirection = Direction.Right;
            }
        }
        private void Play1()
        {
            var firstPoint = _snake.First();

            (int dx, int dy) = firstPoint.GetPoint(_curD);
            int fx = firstPoint.Point.X + dx;
            var fy = firstPoint.Point.Y + dy;
            if (fx >= MapColumn || fx < 0 || fy >= MapRow || fy < 0)
            {
                Stop();
                if (GameOverCallback != null)
                {
                    Task.Factory.StartNew(() =>
                    {
                        GameOverCallback();
                    });
                }
            }
            else
            {
                bool isAdd = false;
                if (fx == TargetPoint.X && fy == TargetPoint.Y)
                {
                    isAdd = true;
                    UpdateTarget();
                    ++_snakeCount;
                    if (SuccessCallback != null)
                    {
                        Task.Factory.StartNew(() =>
                        {
                            SuccessCallback(_snakeCount);
                        });
                    }
                }

                Cell last = null;
                if (isAdd)
                    last = _snake.Last().Clone() as Cell;

                var lastDirection = firstPoint.SnakeDirection;
                firstPoint.SnakeDirection = _curD;
                for (int i = 1; i < SnakeLength; i++)
                {
                    var item = _snake[i];
                    var temp = item.SnakeDirection;
                    item.SnakeDirection = lastDirection;
                    lastDirection = temp;
                }

                if (isAdd)
                    _snake.Add(last);

                this.Invalidate();
            }
        }
        private void UpdateTarget()
        {
            int rx = _ran.Next(0, MapColumn - 1);
            int ry = _ran.Next(0, MapRow - 1);
            while (_snake.Any(t => t.Point.X == rx && t.Point.Y == ry))
            {
                rx = _ran.Next(0, MapColumn - 1);
                ry = _ran.Next(0, MapRow - 1);
            }
            TargetPoint = new Point(rx, ry);
        }

        public void Start()
        {
            _timer.Start();
        }
        public void Stop()
        {
            _timer.Stop();
        }
        public void Reset()
        {
            _snake = new List<Cell>(2);
            int startX = MapRow >> 1;
            int startY = MapColumn >> 1;
            _snake.Add(new Cell
            {
                Point = new Point(startX, startY),
            });
            _snake.Add(new Cell
            {
                Point = new Point(startX - 1, startY)
            });
            _snake.Add(new Cell { Point = new Point(startX - 2, startY) });
            UpdateTarget();
            _snakeCount = 0;
            _curD = Direction.Right;

            this.Invalidate();
        }
        private Direction _curD = Direction.Right;
        public Direction SnakeDirection
        {
            get => _snake.First().SnakeDirection;
            set
            {
                if (_curD == value) return;
                //var first = _snake.First();
                //(int dx, int dy) = first.GetPoint(value);
                //var firstPoint = _snake.First();
                //int fx = firstPoint.Point.X + dx;
                //var fy = firstPoint.Point.Y + dy;
                //var secondPoint = _snake[1];
                //if (fx == secondPoint.Point.X && fy == secondPoint.Point.Y) return;

                _curD = value;
            }
        }
        private int _row = 60;
        public int MapRow
        {
            get
            {
                return _row;
            }
            set
            {
                if (value > 1)
                {
                    _row = value;
                    this.Invalidate();
                }
            }
        }
        private int _column = 60;
        public int MapColumn
        {
            get { return _column; }
            set
            {
                if (value > 1)
                {
                    _column = value;
                    this.Invalidate();
                }
            }
        }
        public int SnakeLength
        {
            get => _snake.Count;
        }
        public Point TargetPoint { get; set; }
        protected override void OnPaint(PaintEventArgs e)
        {
            Stopwatch sw = Stopwatch.StartNew();
            e.Graphics.Smooth();

            e.Graphics.DrawRectangle(penBlack, 0, 0, this.Width - 1, this.Height - 1);

            int width = this.Width - Padding.Left - Padding.Right;
            int height = this.Height - Padding.Top - Padding.Bottom;
            int size = Math.Min(width, height);
            int rowSize = size / _row;
            int columnSize = size / _column;
            int panX = width - columnSize * _column;
            int panY = height - rowSize * _row;
            width -= panX;
            height -= panY;
            panX = Padding.Left + (panX >> 1);
            panY = Padding.Top + (panY >> 1);

            //DrawBlock(e.Graphics, width, height, panX, panY, rowSize, columnSize);
            DrawSnake(e.Graphics, width, height, panX, panY, rowSize, columnSize);
            DrawTarget(e.Graphics, width, height, panX, panY, rowSize, columnSize);
            e.Graphics.Smooth(false);
            sw.Stop();
            Trace.WriteLine(sw.ElapsedMilliseconds);
        }
        protected override void OnSizeChanged(EventArgs e)
        {
            this.Invalidate();
        }
        Pen penBlack = new Pen(Color.Black);
        Pen pen = new Pen(Color.LightGray);
        Brush brushBlack = new SolidBrush(Color.Black);
        Brush brushRed = new SolidBrush(Color.Red);
        private void DrawBlock(Graphics g, int width, int height, int panX, int panY, int rowSize, int columnSize)
        {
            for (int i = 0; i <= _row; i++)
            {
                int y = panY + rowSize * i;
                g.DrawLine(pen, panX, y, width + panX, y);

            }
            for (int i = 0; i <= _column; i++)
            {
                int x = panX + columnSize * i;
                g.DrawLine(pen, x, panY, x, panY + height);
            }
        }
        private void DrawSnake(Graphics g, int width, int height, int panX, int panY, int rowSize, int columnSize)
        {
            for (int i = SnakeLength - 1; i > -1; i--)
            {
                var snake = _snake[i];
                int x = panX + snake.Point.X * columnSize;
                int y = panY + snake.Point.Y * rowSize;

                if (i == 0)
                {
                    g.FillRectangle(brushRed, x, y, columnSize, rowSize);
                }
                else
                {
                    g.FillRectangle(brushBlack, x, y, columnSize, rowSize);
                }
            }
        }
        private void DrawTarget(Graphics g, int width, int height, int panX, int panY, int rowSize, int columnSize)
        {
            int x = panX + TargetPoint.X * columnSize;
            int y = panY + TargetPoint.Y * rowSize;
            g.FillEllipse(brushRed, x, y, columnSize, rowSize);
        }
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            penBlack.Dispose();
            pen.Dispose();
            brushBlack.Dispose();
            brushRed.Dispose();
        }
    }
}
